<?php

/**
 * SimplifyPHP Framework
 *
 * This file is part of SimplifyPHP Framework.
 *
 * SimplifyPHP Framework is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * SimplifyPHP Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Rodrigo Rutkoski Rodrigues, <rutkoski@gmail.com>
 */

/**
 *
 *
 * @author Rodrigo Rutkoski Rodrigues <rutkoski@gmail.com>
 * @package Simplify_Kernel_Core
 */
class Router
{

  /**
   * @var array
   */
  protected $connects = array();

  /**
   * @var boolean
   */
  protected $init = false;

  protected function init()
  {
    if (! $this->init) {
      $this->init = true;

      if (s::config()->has('routes')) {
        $routes = s::config()->get('routes');

        foreach ($routes as $id => $route) {
          $this->connect($id, sy_get_param($route, 0), sy_get_param($route, 1), sy_get_param($route, 2));
        }
      }
    }
  }

  public function build($id, $params = array())
  {
    $this->init();

    if (is_array($id)) {
      $params = $id;

      $found = false;
      foreach ($this->connects as $id => $connect) {
        $defaults = array_merge($connect['defaults'], $params);
        $required = $connect['required'];

        $names = array_flip($connect['names']);
        $diff = array_diff_key($params, $names);

        if (count($diff)) continue;

        $diff = array_diff($required, array_keys($defaults));

        if (empty($diff)) {
          $found = true;

          foreach ($params as $name => $value) {
            //
          }

          foreach ($connect['match'] as $name => $match) {
            if (is_string($match)) {
              if (isset($defaults[$name]) && ! preg_match($match, $defaults[$name])) {
                $found = false;
              }
            }
            elseif ($defaults[$name] != $match) {
              $found = false;
            }
          }
        }

        if ($found) break;
      }
die();
      if (! $found) {
        throw new RouterException("Could not find matching route");
      }
    }
    elseif (! isset($this->connects[$id])) {
      throw new RouterException("Unknow route id: <b>$id</b>");
    }

    extract($this->connects[$id]);

    $words = preg_split('#/#', $route, -1, PREG_SPLIT_NO_EMPTY);

    $route = '';

    if (! empty($words)) {
      $required = '';

      while (count($words)) {
        $word = array_shift($words);

        if ($word == ':args') {
          if (! empty($params)) {
            $word = implode('/', $params);

            $route .= $required . '/' . $word;

            $required = '';
          }
        }

        elseif (strpos($word, ':') === 0) {
          $name = substr($word, 1);

          if (isset($params[$name])) {
            $word = $params[$name];

            unset($params[$name]);

            if ($word != sy_get_param($defaults, $name)) {
              $route .= $required . '/' . $word;

              $required = '';
            }

            else {
              $required .= '/' . $word;
            }
          }

          elseif (isset($defaults[$name])) {
            $word = $defaults[$name];

            $required .= '/' . $word;
          }

          else {
            $word = null;
          }

          if (isset($match[$name]) && ! preg_match($match[$name], $word)) {
            throw new RouterException("Missing route parameter: <b>$name</b>");
          }
        }

        else {
          $route .= '/' . $word;
        }
      }
    }

    if (empty($route)) $route = '/';

    return $route;
  }

  public function connect($id, $route, $defaults = array(), $match = array())
  {
    $this->init();

    $words = array_filter(explode('/', $route));

    $regex = array();
    $names = array();
    $required = array();

    $index = 0;

    foreach ($words as &$word) {
      if ($word == '*') {
        $regex[] = '(?:/(.*))?';
        $match['args'] = '#^(.*)?#';

        $names[$index++] = 'args';

        $word = ':args';
      }

      else if (strpos($word, ':') === 0) {
        $wildcard = substr($word, 1);

        $names[$index++] = $wildcard;

        if (isset($match[$wildcard])) {
          if (preg_match('#^' . $match[$wildcard] . '$#', '')) {
            $regex[] = '(?:/(' . $match[$wildcard] . ')?)?';
          }
          else {
            $regex[] = '/(' . $match[$wildcard] . ')';

            $required[] = $wildcard;
          }

          $match[$wildcard] = '#^' . $match[$wildcard] . '$#';
        }

        elseif (isset($defaults[$wildcard])) {
          $regex[] = '(?:/([^/]+))?';

          $match[$wildcard] = '#^([^/]+)?$#';
        }

        else {
          $required[] = $wildcard;

          $regex[] = '/([^/]+)';

          $match[$wildcard] = '#^[^/]+$#';
        }
      }

      else {
        $regex[] = '/' . $word;
      }
    }

    $route = '/' . implode('/', $words);

    $regex = implode('', $regex);
    $regex = '#^' . $regex . '/*$#';

    $connect = array(
      'route' => $route, 'regex' => $regex, 'names' => $names, 'defaults' => $defaults, 'match' => $match, 'required' => $required
    );

    $this->connects[$id] = $connect;
  }

  public function parse($route)
  {
    $this->init();

    $keys = array_keys($this->connects);
    $f = count($keys);
    $i = 0;

    while ($i < $f && ! preg_match($this->connects[$keys[$i]]['regex'], $route, $r)) {
      $i++;
    }

    if ($i < $f) {
      $data = $this->_parse($r, $keys[$i]);
    }

    if (empty($data)) {
      throw new RouterException("Unknown route <b>$route</b>");
    }

    return $data;
  }

  protected function _parse($r, $i)
  {
    array_shift($r);

    $connect = $this->connects[$i];

    $data = $connect['defaults'];
    $data['params'] = array();

    foreach ($connect['names'] as $key => $name) {
      $value = sy_get_param($r, $key, sy_get_param((array) $connect['defaults'], $name));

      if ($name == 'args') {
        $params = array_filter(explode('/', $value));
        $data['params'] += $params;
      }
      elseif (in_array($name, array('module', 'controller', 'action'))) {
        $data[$name] = $value;
      }
      else {
        $data['params'][$name] = $value;
      }
    }

    foreach ((array) $connect['defaults'] as $name => $value) {
      if (preg_match_all('/\:([^\/]+)/', $value, $r)) {
        foreach ($r[1] as $wildcard) {
          $data[$name] = str_replace(':' . $wildcard, $data[$wildcard], $data[$name]);
        }
      }
    }

    return $data;
  }

}
